#include "lsv_rcache.h"
#include "lsv_log.h"
#include "lsv_volume.h"
#include "volume_proto.h"
#include "list.h"


int lsv_rcache_ssd_chunk_init(lsv_volume_proto_t *volume_proto){
        int err = 0;
        lsv_s32_t i = 0;
        lsv_s32_t rewrite = 0;
        lsv_u32_t *chunk_ids;
        lsv_rcache_t * rcache = (lsv_rcache_t *)volume_proto->rcache;

        // 4 * 4K
        lsv_s32_t malloc_size = LSV_RCACHE_CHUNK_NUM_MAX_L2 * sizeof(lsv_u32_t);

        err = ymalloc((void **)&chunk_ids, malloc_size);
        if(unlikely(err)){
                err = -ENOMEM;
                DERROR("rcache malloc memory for storage ssd chunk_id for cache chunk, err: %d\n",err);
                return err;
        }
        memset(chunk_ids, 0, malloc_size);

        if(volume_proto->u.system_power_on == 0){
                // TODO 可以延后再分配
                // 正常启动，需要为rcache的ssd层申请存储空间，用于存放chunk
                err = lsv_volume_chunk_malloc_batch(volume_proto, LSV_RCACHE_STORAGE_TYPE,
                                LSV_RCACHE_CHUNK_NUM_MAX_L2, chunk_ids);
                if(unlikely(err)){
                        err = -EIO;
                        DERROR("rcache malloc ssd storage for cache chunk, err: %d\n",err);
                        goto EXIT;
                }
        }else{
                //非正常启动，需要读取上次运行时申请的ssd空间，继续作为存放chunk的ssd cache
                err = lsv_rcache_read_data(volume_proto,
                                (lsv_s8_t *)chunk_ids,
                                LSV_PRIM_CHUNK_ID,
                                volume_proto->u.rcache_page_id * LSV_PAGE_SIZE,
                                malloc_size);
                if(unlikely(err)){
                        err = -EIO;
                        DERROR("call lsv_rcache_read_data read rcache's chunk_id failed, err: %d\n", err);
                        goto EXIT;
                }

        }

        for(i = 0; i < LSV_RCACHE_CHUNK_NUM_MAX_L2; i++){
                lsv_rcache_chunk_l2_unit * cl2u;
                err = ymalloc((void **)&cl2u, sizeof(lsv_rcache_chunk_l2_unit));
                if(unlikely(err)){
                        err = -ENOMEM;
                        DERROR("rcache malloc for cl2u failed, err: %d\n", err);
			goto EXIT;;
                }

                memset(cl2u, 0, sizeof(lsv_rcache_chunk_l2_unit));
                INIT_LIST_HEAD(&cl2u->l2_lru_hook);
                INIT_LIST_HEAD(&cl2u->index_list);
                lsv_rwlock_init(&cl2u->rwlock);
                cl2u->l2_chunk_id = chunk_ids[i];
                list_add_tail(&cl2u->l2_lru_hook, &rcache->l2_lru);
        }

        if(volume_proto->u.system_power_on == 1 && rewrite == 0){
		goto EXIT;
        }

        err = lsv_rcache_write_data(volume_proto,
                        (lsv_s8_t *)chunk_ids,
                        LSV_PRIM_CHUNK_ID,
                        volume_proto->u.rcache_page_id * LSV_PAGE_SIZE,
                        malloc_size,
                        LSV_RCACHE_STORAGE_TYPE);
        if(unlikely(err)) {
                err = -EIO;
                DERROR("write rcache's chunk_ids into disk failed\n");
                goto EXIT;
        }
EXIT:
	free(chunk_ids);
        return err;
}

int lsv_rcache_ssd_chunk_free(lsv_volume_proto_t *volume_proto){
        int ret = 0;
        lsv_s32_t i = 0;
        struct list_head *pos,*n;
        lsv_u32_t *chunk_ids;
        lsv_rcache_t * rcache = (lsv_rcache_t *)volume_proto->rcache;

        lsv_s32_t malloc_size = LSV_RCACHE_CHUNK_NUM_MAX_L2 * sizeof(lsv_u32_t);

        ret = ymalloc((void **)&chunk_ids, malloc_size);
        if(unlikely(ret)){
                ret = -ENOMEM;
                DERROR("rcache malloc memory for storage ssd chunk_id for cache chunk, ret: %d\n",ret);
                return ret;
        }
        memset(chunk_ids, 0, malloc_size);

        list_for_each_safe(pos, n, &rcache->l2_lru){
                lsv_rcache_chunk_l2_unit *l2u = list_entry(pos, lsv_rcache_chunk_l2_unit, l2_lru_hook);
                chunk_ids[i++] = l2u->l2_chunk_id;
                list_del_init(&l2u->l2_lru_hook);
                list_del_init(&l2u->index_list);
                free(l2u);
        }

        ret = lsv_volume_chunk_free_batch(volume_proto, LSV_RCACHE_STORAGE_TYPE, LSV_RCACHE_CHUNK_NUM_MAX_L2, chunk_ids);
        if(unlikely(ret)){
                ret = -EIO;
                DERROR("call lsv_volume_chunk_free_batch, free rcache's chunk_ids failed, ret: %d\n", ret);
                return ret;
        }


        // TODO 如果系统有时间，可以将ssd中保存的rcache的chunk_id清掉，否则，系统在正常启动的时候，
        // 需要判断上次是否有未释放的空间，如果有，则读之，使用，否则，申请
        memset(chunk_ids, 0, malloc_size);
        ret = lsv_rcache_write_data(volume_proto,
                        (lsv_s8_t *)chunk_ids,
                        LSV_PRIM_CHUNK_ID,
                        volume_proto->u.rcache_page_id * LSV_PAGE_SIZE,
                        malloc_size,
                        LSV_RCACHE_STORAGE_TYPE);
        if(unlikely(ret)) {
                ret = -EIO;
                DERROR("write rcache's chunk_ids into disk failed\n");
                return ret;
        }

	free(chunk_ids);

        return ret;
}

typedef struct {
        lsv_volume_proto_t *lsv_info;
        void *ptr;
        lsv_s32_t need_free;
        lsv_rcache_chunk_l2_unit * l2u;
} write_ssd_ctx_t;

static void _do_write_ssd(void *arg) {
        int ret;
        write_ssd_ctx_t *ctx = arg;
	lsv_volume_proto_t *volume_proto = (lsv_volume_proto_t *)ctx->lsv_info;
        lsv_rcache_t *rcache = (lsv_rcache_t *)volume_proto->rcache;

        ret = lsv_rcache_l2_cache_write_data(ctx->lsv_info,
                        ctx->ptr,
                        ctx->l2u->l2_chunk_id,
                        0,
                        LSV_CHUNK_SIZE,
                        LSV_RCACHE_STORAGE_TYPE);
        if (ret < 0) {
                DERROR("write  into l2_cache, chunk_id: %u, ret: %d\n ", ctx->l2u->l2_chunk_id, ret);
                ctx->l2u->status = LSV_RCACHE_NULL;
        		goto EXIT;
        }

        ctx->l2u->status = LSV_RCACHE_IN_SSD;
        co_cond_broadcast(&rcache->l2_cond, ret);

        if(ctx->need_free){
                yfree((void **)&ctx->ptr);
        }
EXIT:
        yfree((void **)&ctx);
}

int lsv_rcache_schedule_task(lsv_volume_proto_t *volume_proto, lsv_rcache_chunk_l2_unit *l2u, void *buf)
{
	lsv_s32_t ret = 0;
	write_ssd_ctx_t *ctx;

        ret = ymalloc((void **)&ctx, sizeof(write_ssd_ctx_t));
        if (unlikely(ret))
                YASSERT(0);

        ctx->lsv_info = volume_proto;
        ctx->ptr = buf;
        ctx->l2u = l2u;

        ctx->need_free = 1;
        schedule_task_new("write_ssd", _do_write_ssd, ctx, -1);

	return 0;
}

// 如果LSV_RCACHE_CHUNK_NUM_MAX > 0, 则当内存chunk满时，将换出的chunk放到ssd的L2_cache中,
// 即相当于在内存中分配一定内存用于缓存读出来的chunk，那么ssd中的chunk空间，用于缓存内存中换出的chunk即可
int lsv_rcache_l2_cache_swap(lsv_volume_proto_t *volume_proto, lsv_rcache_chunk_unit *cu){
        int ret = 0;
        lsv_rcache_t *rcache = NULL;
        lsv_rcache_chunk_l2_unit * l2u = NULL;
        lsv_rcache_chunk_l2_unit * tl2u = NULL;
        lsv_s32_t l2_hash_key = 0;
        lsv_s8_t *buf;
        struct list_head *pos, *n;

        YASSERT(LSV_CHUNK_NULL != cu->chunk_id);

        rcache = (lsv_rcache_t *)volume_proto->rcache;

        /*清楚所有chunk_id的索引*/
        //通过标记使其失效，后期在回收
        //lsv_rcache_l2_cache_hash_index_regc(volume_proto, cu->chunk_id);
        lsv_rcache_l2_cache_hash_index_regc_with_mark(volume_proto, cu->chunk_id);

        //TODO 缓存内存换出的chunk，放到ssd中
        //TOTO bitmap通知gc valueup的时候，是否，同样告知chunk_hash_index，统计一个chunk_hash_index的回收价值，若回收价值加高，则直接丢弃，不在向ssd转移
        //此处若根据gc valueup统计回收价值的话，可以针对每个cu，添加invalid_bitmap，标识每个page是否可用，目前，cu的策略，只要产生一个gc valueup，则将其对应的cu无效掉
        //此策略可以使用在lsv_rcache_lookup_l2_cache中，由于没有内存部分，可以通过gc valueup值，判断是否写回到ssd中

        while(1){
        	list_for_each_prev_safe(pos, n, &rcache->l2_lru){
        		tl2u = list_entry(pos, lsv_rcache_chunk_l2_unit, l2_lru_hook);
        		if(tl2u->status != LSV_RCACHE_IN_HDD){
        			l2u = tl2u;
        			break;
        		}
        	}
	
        	if(NULL != l2u){
        		break;
        	}
        	//TODO 需要yield退出，然后等待有l2_lru释放锁后，再进入获取锁
        	co_cond_wait(&rcache->l2_cond);
        }

        /*当cu对应的chunk_id被回收时，不需要再写到ssd中*/
        if(cu->hit_count == 0){
                return ret;
        }

        list_del_init(&l2u->l2_lru_hook);
        list_add(&l2u->l2_lru_hook, &rcache->l2_lru);

        if(!list_empty(&l2u->index_list)){
                list_del_init(&l2u->index_list);
        }

        l2u->chunk_id = cu->chunk_id;
        l2u->status = LSV_RCACHE_IN_HDD;
        //添加索引
        l2_hash_key = HASH_KEY1(l2u->chunk_id);
        list_add(&l2u->index_list, &rcache->l2_chunk_hash_index[l2_hash_key].list);

        /*拷贝一个副本，写入ssd cache中，否则，会产生不一致*/

        ret = ymalloc((void *)&buf, LSV_CHUNK_SIZE);
        if(unlikely(ret)){
                ret = -ENOMEM;
                DERROR("malloc for buf failed\n");
                return ret;
        }
        memcpy(buf, cu->buf, LSV_CHUNK_SIZE);

        //写回到ssd cache中
        lsv_rcache_schedule_task(volume_proto, l2u, buf);

        return ret;
}

//TODO 是否要改变l2_cache的逻辑结构，先将l2u插入到哈希表中，然后，再读，最后写，写回需要使用异步操作
int lsv_rcache_lookup_l2_cache(lsv_volume_proto_t *volume_proto, lsv_u32_t chunk_id,
                lsv_s32_t chunk_offset, lsv_u64_t off, lsv_s32_t size, buffer_t *append_buf) {
        int ret = 0;
        lsv_s32_t page_offset = off % LSV_PAGE_SIZE;
        lsv_rcache_t *rcache = NULL;
        lsv_rcache_chunk_l2_unit * l2u = NULL;
        lsv_rcache_chunk_l2_unit * tl2u = NULL;
        lsv_s32_t l2_hash_key = 0;
        char * buf;
        struct list_head *pos, *n;

        YASSERT(LSV_CHUNK_NULL != chunk_id);

        //ret = lsv_rcache_hash_index_lookup(volume_proto, chunk_id, chunk_offset, off, size, append_buf);
        ret = lsv_rcache_l2_cache_hash_index_lookup(volume_proto, chunk_id, chunk_offset, off, size, append_buf);
        if(ret == size){
                volume_proto->read_rc_hit++;
                return ret;
        }
        volume_proto->read_rc_miss++;

        rcache = (lsv_rcache_t *)volume_proto->rcache;

        /*清除所有chunk_id的索引*/
        lsv_rcache_l2_cache_hash_index_regc_with_mark(volume_proto, chunk_id);

        //TODO 将读上来的chunk写入ssd cache中，并更新相应的l2_chunk_hash_index
        while(1){
        	list_for_each_prev_safe(pos, n, &rcache->l2_lru){
        		tl2u = list_entry(pos, lsv_rcache_chunk_l2_unit, l2_lru_hook);
        		if(tl2u->status != LSV_RCACHE_IN_HDD){
        			l2u = tl2u;
        			break;
        		}
        	}
        	if(NULL != l2u){
        			break;
        	}
        	co_cond_wait(&rcache->l2_cond);
        }
	
        list_del_init(&l2u->l2_lru_hook);
        list_add(&l2u->l2_lru_hook, &rcache->l2_lru);

        //索引回收
        if(!list_empty(&l2u->index_list)){
                list_del_init(&l2u->index_list);
        }

        l2u->chunk_id = chunk_id;
        l2u->status = LSV_RCACHE_IN_HDD;

        //load chunk
        ret = ymalloc((void *)&buf, LSV_CHUNK_SIZE);
        if(unlikely(ret)){
                ret = -ENOMEM;
                DERROR("malloc for buf failed\n");
                return ret;
        }

        ret = lsv_log_read(volume_proto, chunk_id, (lsv_s8_t *)buf);
        if(ret){
                DERROR("call lsv_log_slog_read read chunk_id[%d] error:%d\n", chunk_id, ret);
                return ret;
        }
        mbuffer_appendmem(append_buf, buf + chunk_offset + page_offset, size);

        //将查询的page插入到对应的page
        lsv_rcache_pu_insert(volume_proto, chunk_id, chunk_offset, off, size, buf);

        //TOTO bitmap通知gc valueup的时候，是否，同样告知chunk_hash_index，统计一个chunk_hash_index的回收价值，若回收价值加高，则直接丢弃，不在向ssd转移
        //此处若根据gc valueup统计回收价值的话，可以针对每个cu，添加invalid_bitmap，标识每个page是否可用，目前，cu的策略，只要产生一个gc valueup，则将其对应的cu无效掉
        //此策略可以使用在lsv_rcache_lookup_l2_cache中，由于没有内存部分，可以通过gc valueup值，判断是否写回到ssd中
        //查询logctrl的valueup值，如果valueup小于10，则download，否则，丢弃，如果查不到对应的logctrl，说明chunk在老年代，根据随机数进行download
        ret = lsv_rcache_l2u_download_judge(volume_proto, chunk_id);
        if(ret == 0){
        		return size;
        }

        l2_hash_key = HASH_KEY1(l2u->chunk_id);
        list_add(&l2u->index_list, &rcache->l2_chunk_hash_index[l2_hash_key].list);

        //写回到ssd cache中
        lsv_rcache_schedule_task(volume_proto, l2u, buf);

        lsv_rwunlock(&l2u->rwlock);
        co_cond_broadcast(&rcache->l2_cond, ret);
        return size;
}
